/*
 * File Name:               Math2D.cs
 * 
 * Description:             2D数学计算库
 *                          函数统一命名风格 以返回值为前缀 写清楚入参
 *                          并写出详细的注释
 *                          
 *                          距离 Distance
 *                          点 Point
 *                          方向 Direction
 *                          直线 Line
 *                          线段 Segment
 *                          平面 Plane
 *                          矩形区域 RectArea
 *                          三角区域 TriangleArea
 *                          是否 Is
 *                          角度 Angle
 *                          
 * Author:                  lisiyu <576603306@qq.com>
 * Create Date:             2017/01/19
 */

using System;
using Math2DStruct;

public class Math2D
{
    #region 角度
    /// <summary>
    /// 两个向量之间的夹角
    /// 值域为 0到180 -180到0
    /// 顺时针为正
    /// </summary>
    /// <param name="from"></param>
    /// <param name="to"></param>
    /// <returns></returns>
    public static XNumber Angle_Dir2Dir(XVector2 from, XVector2 to)
    {
        if (/*from == XVector2.zero || to == XVector2.zero
            || from == to
            || */
            (XIntMath.Approximately(from.x, XNumber.zero) && XIntMath.Approximately(from.y, XNumber.zero))
            || (XIntMath.Approximately(to.x, XNumber.zero) && XIntMath.Approximately(to.y, XNumber.zero)))
            return XNumber.zero;

        var angle = XVector2.Angle(from, to);

        if (angle == XNumber.zero)
            return angle;

        if (XVector3.Cross(from.x0z, to.x0z).y < 0)
        {
            angle = -angle;
        }

        return angle;
    }
    #endregion 角度

    #region 点
    /// <summary>
    /// 获取两个起点出发的向量交叉点
    /// </summary>
    /// <param name="posA">直线 A 上任意一点</param>
    /// <param name="dirA">直线 A 方向</param>
    /// <param name="posB">直线 B 上任意一点</param>
    /// <param name="dirB">直线 B 方向</param>
    /// <returns></returns>
    public static XVector2? Point_Cross_LineAndLine(XVector2 posA, XVector2 dirA, XVector2 posB, XVector2 dirB)
    {
        if (dirB.x * dirA.y - dirB.y * dirA.x == 0)
        {
            // 两条直线平行 没有交点
            return null;
        }

        var z = (posB.y * dirA.x - posB.x * dirA.y + posA.x * dirA.y - posA.y * dirA.x) / (dirB.x * dirA.y - dirB.y * dirA.x);
        return posB + dirB * z;
    }

    /// <summary>
    /// 两直线交点;
    /// </summary>
    /// <param name="lineA"></param>
    /// <param name="lineB"></param>
    /// <returns></returns>
    public static XVector2? Point_Cross_LineAndLine(Line lineA, Line lineB)
    {
        return Point_Cross_LineAndLine(lineA.startPoint, lineA.direction, lineB.startPoint, lineB.direction);
    }

    /// <summary>
    /// 两个线段之间的交点
    /// </summary>
    /// <param name="lineApos1"></param>
    /// <param name="lineApos2"></param>
    /// <param name="lineBpos1"></param>
    /// <param name="lineBpos2"></param>
    /// <returns></returns>
    public static XVector2? Point_Cross_SegmentAndSegment(XVector2 lineApos1, XVector2 lineApos2, XVector2 lineBpos1, XVector2 lineBpos2)
    {
        var ret = Point_Cross_LineAndLine(lineApos1, lineApos2 - lineApos1, lineBpos1, lineBpos2 - lineBpos1);
        if (!ret.HasValue)
            return null;        // 平行

        var lineAlength = (lineApos2 - lineApos1).magnitude;
        if (lineAlength < (ret.Value - lineApos1).magnitude
            || lineAlength < (ret.Value - lineApos2).magnitude)
            return null;

        var lineBlength = (lineBpos2 - lineBpos1).magnitude;
        if (lineBlength < (ret.Value - lineBpos1).magnitude
            || lineBlength < (ret.Value - lineBpos2).magnitude)
            return null;

        return ret;
    }

    /// <summary>
    /// 两个线段之间的交点
    /// </summary>
    /// <param name="l1"></param>
    /// <param name="l2"></param>
    /// <returns></returns>
    public static XVector2? Point_Cross_SegmentAndSegment(LineSegment l1, LineSegment l2)
    {
        return Point_Cross_SegmentAndSegment(l1.pos1, l1.pos2, l2.pos1, l2.pos2);
    }

    /// <summary>
    /// 线段和直线的交点
    /// </summary>
    /// <param name="segmentPosA"></param>
    /// <param name="segmentPosB"></param>
    /// <param name="linePos"></param>
    /// <param name="lineDir"></param>
    /// <returns></returns>
    public static XVector2? Point_Cross_SengmentAndLine(XVector2 segmentPosA, XVector2 segmentPosB, XVector2 linePos, XVector2 lineDir)
    {
        var ret = Point_Cross_LineAndLine(segmentPosA, segmentPosB - segmentPosA, linePos, lineDir);
        if (ret == null)
            return null;

        var tmoPos = ret.Value;
        if ((tmoPos.x - segmentPosA.x) * (tmoPos.x - segmentPosB.x) > 0)
            return null;

        if ((tmoPos.y - segmentPosA.y) * (tmoPos.y - segmentPosB.y) > 0)
            return null;

        return ret;
    }

    /// <summary>
    /// 点到直线做一条垂线, 返回垂直线的交点;
    /// </summary>
    /// <param name="point"></param>
    /// <param name="line"></param>
    /// <returns></returns>
    public static XVector2 PointToLineVertical(XVector2 point, Line line)
    {
        var p2s = point - line.startPoint;
        var crossPoint = XVector2.Dot(p2s, line.direction) * line.direction + line.startPoint;
        return crossPoint;
    }

    /// <summary>
    /// 点到直线做一条垂线, 返回垂直线的交点;
    /// </summary>
    /// <param name="point"></param>
    /// <param name="lineStartPoint"></param>
    /// <param name="lineDir"></param>
    /// <returns></returns>
    public static XVector2 PointToLineVertical(XVector2 point, XVector2 lineStartPoint, XVector2 lineDir)
    {
        var p2s = point - lineStartPoint;
        var crossPoint = XVector2.Dot(p2s, lineDir) * lineDir + lineStartPoint;
        return crossPoint;
    }

    public static void Cross_CircleAndSegment(XVector2 pos_circle, XNumber radius_circle, XVector2 pos_seg01, XVector2 pos_seg02, out XVector2? cross01, out XVector2? cross02)
    {
        cross01 = null;
        cross02 = null;

        var circle = new Circle(pos_circle, radius_circle);
        var ret = circle.PointWithLineSegment(pos_seg01, pos_seg02);
        var count = ret.points.Count;
        if (count == 1) cross01 = ret.points[0];
        if (count == 2) cross02 = ret.points[1];


        //cross01 = null;
        //cross02 = null;

        //var A = (pos_seg02.x - pos_seg01.x) * (pos_seg02.x - pos_seg01.x) + (pos_seg02.y - pos_seg01.y) * (pos_seg02.y - pos_seg01.y);
        //if(A == 0)
        //    return;

        //var B = 2 * ((pos_seg02.x - pos_seg01.x) * (pos_seg01.x - pos_circle.x) + (pos_seg02.y - pos_seg01.y) * (pos_seg01.y - pos_circle.y));
        //var C = pos_circle.x * pos_circle.x + pos_circle.y * pos_circle.y + pos_seg01.x * pos_seg01.x + pos_seg01.y * pos_seg01.y - 2 * (pos_circle.x * pos_seg01.x + pos_circle.y * pos_seg01.y) - radius_circle * radius_circle;

        //// 这里可以算出是否和直线有交点 切线直接忽略
        //long bb4ac = B.raw / 1024 * B.raw - 4 * A.raw / 1024 * C.raw;
        //if(bb4ac <= 0)
        //    return;

        //var bb4ac_sqr = XNumber.create_row(XIntMath.Sqrt_Long(bb4ac) * 32);
        //var u1 = (-B + bb4ac_sqr) / 2 / A;
        //if(u1 > 0 && u1 < 1)
        //{
        //    cross01 = pos_seg01 + u1 * (pos_seg02 - pos_seg01);
        //}

        //var u2 = (-B - bb4ac_sqr) / 2 / A;
        //if (u2 > 0 && u2 < 1)
        //{
        //    cross02 = pos_seg01 + u2 * (pos_seg02 - pos_seg01);
        //}
    }
    #endregion

    #region 距离
    /// <summary>
    /// 计算点和直线的距离 ， 直线用两个点表示
    /// </summary>
    /// <param name="point"></param>
    /// <param name="linePos1"></param>
    /// <param name="linePos2"></param>
    /// <returns></returns>
    public static XNumber DisPointToLinePoint(XVector2 point, XVector2 linePos1, XVector2 linePos2)
    {
        var line = new Line(linePos1, linePos1 - linePos2);
        return DisPointToLine(point, line);
    }

    /// <summary>
    /// 计算点和直线的距离
    /// </summary>
    /// <param name="point"></param>
    /// <param name="line"></param>
    /// <returns></returns>
    public static XNumber DisPointToLine(XVector2 point, Line line)
    {
        var crossPoint = PointToLineVertical(point, line);
        return XVector2.Distance(crossPoint, point);
    }

    public static XNumber Disance_Point2Line(XVector2 point, XVector2 linePos01, XVector2 linePos02)
    {
        var dir = (linePos01 - linePos02).normalized.cross;
        return XIntMath.Abs(XVector2.Dot(linePos01 - point, dir));
    }

    public static XNumber Disance_Point2LineDir(XVector2 point, XVector2 linePos, XVector2 lineDir)
    {
        return XIntMath.Abs(XVector2.Dot(linePos - point, lineDir));
    }
    #endregion 距离

    #region 位置关系判断
    /// <summary>
    /// 判断点p 是否在三角形abc内部
    /// </summary>
    /// <param name="p"></param>
    /// <param name="a"></param>
    /// <param name="b"></param>
    /// <param name="c"></param>
    /// <returns></returns>
    public static bool IsPointInTriangle(XVector2 p, XVector2 a, XVector2 b, XVector2 c)
    {
        var signOfTrig = (b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x);
        var signOfAB = (b.x - a.x) * (p.y - a.y) - (b.y - a.y) * (p.x - a.x);
        if (signOfAB * signOfTrig <= 0) return false;
        var signOfCA = (a.x - c.x) * (p.y - c.y) - (a.y - c.y) * (p.x - c.x);
        if (signOfCA * signOfTrig <= 0) return false;
        var signOfBC = (c.x - b.x) * (p.y - c.y) - (c.y - b.y) * (p.x - c.x);
        if (signOfBC * signOfTrig <= 0) return false;
        return true;
    }

    /// <summary>
    /// 判断 dirA 和 dirB 是否在 direction 的同一边
    /// </summary>
    public static bool Is_DirectionInSameSide(XVector2 direction, XVector2 dirA, XVector2 dirB)
    {
        var angle2A = XVector2.Angle(direction, dirA);
        var angle2B = XVector2.Angle(direction, dirB);
        var angleAB = XVector2.Angle(dirA, dirB);

        return angleAB * XNumber.create(1, 10) < angle2A + angle2B;
    }
    #endregion


    #region 圆
    public static bool Intersection(Circle a, Circle b, out XVector2? p1, out XVector2? p2)
    {
        var dis = XVector2.Distance(a.center, b.center);
        var radiusPlus = a.radius + b.radius;
        if (a.center == b.center || dis > radiusPlus || dis < XIntMath.Abs(a.radius - b.radius))
        {
            p1 = null;
            p2 = null;
            return false;
        }
        else if (dis == radiusPlus)
        {
            p1 = (a.center - b.center).normalized * b.radius + b.center;
            p2 = null;
            return true;
        }
        else if (dis == XIntMath.Abs(a.radius - b.radius))
        {
            if (a.radius > b.radius)
                p1 = (b.center - a.center).normalized * a.radius + a.center;
            else
                p1 = (a.center - b.center).normalized * b.radius + b.center;
            p2 = null;
            return true;
        }
        else
        {

            //相对坐标系计算交点;
            var x = (a.radius * a.radius - b.radius * b.radius + dis * dis) / (2 * dis);
            var y1 = XIntMath.Sqrt(a.radius * a.radius - x * x);
            var y2 = -y1;

            p1 = new XVector2(x, y1);
            p2 = new XVector2(x, y2);

            var mT = new Matrix3x3();
            mT.BuildT(a.center);

            var rT = new Matrix3x3();
            var angle = XVector2.Angle180(XVector2.right, b.center - a.center);
            rT.BuildR(angle);

            p1 = rT.Transform(p1.Value);
            p1 = mT.Transform(p1.Value);

            p2 = rT.Transform(p2.Value);
            p2 = mT.Transform(p2.Value);

            return true;
        }
    }
    #endregion

    #region 旋转
    /// <summary>
    /// 球场平面的旋转，旋转轴固定为向上，顺时针旋转传入正角度，逆时针旋转传入负角度
    /// </summary>
    /// <param name="angle"></param>
    /// <param name="vector"></param>
    /// <returns></returns>
    public static XVector2 Rotate_Dir2Dir(XNumber angle, XVector2 vector)
    {
        var normal = XVector3.up;
        var vector3 = XQuaternion.AngleAxis(angle, normal) * vector.x0z;
        return vector3.xz;
    }
    #endregion

    #region 是否
    public static bool is_parallel(XVector2 lhs, XVector2 rhs)
    {
        if (lhs.x == 0 && rhs.x != 0) return false;
        if (lhs.x != 0 && rhs.x == 0) return false;
        if (lhs.y == 0 && rhs.y != 0) return false;
        if (lhs.y != 0 && rhs.y == 0) return false;

        return lhs.x / rhs.x == lhs.y / rhs.y;
    }
    #endregion
    #region tools

    #endregion
}
